#include "stdafx.h"
#include "AliasModelLoader.h"
#include "QuakeAliasModel.h"

using namespace QuakeSandbox;
using namespace IdQuake;

namespace 
{
	byte host_basepal[256*3];
	bool palette_loaded = false;

	template<class T, class V> int IndexOf(const T& list, const V& item)
	{
		int idx = 0;
		for (T::const_iterator iter = list.begin() ; iter != list.end() ; iter++, idx++)
		{
			if (*iter == item)
				return idx;
		}

		OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "Item not found", "");
	}

	inline IdQuake::mtriangle_t* GetTriangles(IdQuake::aliashdr_t* header)
	{
		return reinterpret_cast<IdQuake::mtriangle_t*>(
			(byte *)header + header->triangles);
	}

	inline IdQuake::mdl_t* GetModel(IdQuake::aliashdr_t* header)
	{
		return reinterpret_cast<IdQuake::mdl_t*>(
			(byte *)header + header->model);
	}

	inline IdQuake::trivertx_t* GetVertices(IdQuake::aliashdr_t* header, int frameNumber)
	{
		return reinterpret_cast<IdQuake::trivertx_t*>(
			(byte *)header + header->frames[frameNumber].frame);	
	}

	inline IdQuake::stvert_t* GetStVerts(IdQuake::aliashdr_t* header)
	{
		return reinterpret_cast<IdQuake::stvert_t*>(
			(byte *)header + header->stverts);
	}

	inline IdQuake::maliasskindesc_t* GetSkin(IdQuake::aliashdr_t* header)
	{
		 return reinterpret_cast<IdQuake::maliasskindesc_t*>(
			 (byte *)header + header->skindesc);
	}

	bool IsVertexOnSeam(IdQuake::aliashdr_t* header, int vertex)
	{
		IdQuake::stvert_t* vertices = GetStVerts(header);
		return vertices[vertex].onseam ? true : false;
	}

	const std::vector<int>* GetOnseamVerts(IdQuake::aliashdr_t* header)
	{
		static std::vector<int> onseamVerts;
		int numVertices = GetModel(header)->numverts;

		onseamVerts.clear();

		for (int i=0 ; i<numVertices ; i++)
		{
			if (IsVertexOnSeam(header, i))
			{
				onseamVerts.push_back(i);
			}
		}

		return &onseamVerts;	
	}

	Ogre::Vector3 GetVertex(IdQuake::aliashdr_t* header, int vertex, int frameNumber)
	{
		IdQuake::mdl_t* model = GetModel(header);
		IdQuake::trivertx_t* vertices = GetVertices(header, frameNumber);
		
		return Ogre::Vector3(
			(vertices[vertex].v[0] * model->scale[0]) + model->scale_origin[0],			
			(vertices[vertex].v[2] * model->scale[2]) + model->scale_origin[2],   //rotate vertex 90 degress on Y-Z plane
			(vertices[vertex].v[1] * model->scale[1]) + model->scale_origin[1]);
	}

	Ogre::ColourValue ToColourValue(byte colorIndex)
	{
		byte r = host_basepal[colorIndex*3];
		byte g = host_basepal[colorIndex*3 + 1];
		byte b = host_basepal[colorIndex*3 + 2];

		return Ogre::ColourValue(r/255.0f, g/255.0f, b/255.0f);
	}

	void LoadColorPalette()
	{
		if (palette_loaded)
			return;

		Ogre::DataStreamPtr stream =
				Ogre::ResourceGroupManager::getSingleton().openResource(
					"gfx/palette.lmp",
					Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
					true,
					NULL);

		stream->read(host_basepal, sizeof(host_basepal));
	}

	/** Sets up vertex indices to define the triangles of the model.
		
		When specifying the vertex index for each triangle we have to take 
		"on seam" vertices into account.  If a vertex is on seam and the triangle
		it belongs to is on the back of the model, we have to specify a vertex index
		corresponding to one of the new vertices created during the SetupVertexData step.
	*/
		
	void SetupTriangles(IdQuake::aliashdr_t* header, Ogre::Mesh* mesh)
	{
		const int INDICES_PER_TRIANGLE = 3;
		Ogre::HardwareIndexBufferSharedPtr ibuffer;
		Ogre::SubMesh* submesh = mesh->getSubMesh(0);
		IdQuake::mtriangle_t* triangles = GetTriangles(header);
		IdQuake::mdl_t* model = GetModel(header);
		int numTriangles = model->numtris;

		//create index buffer
		submesh->indexData->indexCount = numTriangles * INDICES_PER_TRIANGLE;
		submesh->indexData->indexStart = 0;
		submesh->indexData->indexBuffer = Ogre::HardwareBufferManager::getSingleton().createIndexBuffer(
			Ogre::HardwareIndexBuffer::IT_16BIT,
			submesh->indexData->indexCount,
			Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY,
			false);

		//fill index buffer
		unsigned short* pIndex = static_cast<unsigned short*>(submesh->indexData->indexBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD));
		const std::vector<int>* onseamVerts = GetOnseamVerts(header);
		
		for (int i=0 ; i<numTriangles ; i++)
		{
			for (int vi=0 ; vi<INDICES_PER_TRIANGLE ; vi++)
			{
				if (!triangles[i].facesfront && IsVertexOnSeam(header, triangles[i].vertindex[vi]))
				{
					*pIndex++ = model->numverts + IndexOf(*onseamVerts, triangles[i].vertindex[vi]);
				}
				else
				{
					*pIndex++ = triangles[i].vertindex[vi];
				}
			}
		}

		submesh->indexData->indexBuffer->unlock();
	}

	/** Reads vertex data from the quake model and sends it to a vertex buffer.

		Creating the correct vertices is tricky due to the "on seam" verts - vertices
		that are shared between back-of-the-model triangles and front-of-the-model triangles.
		Any time there is an on seam vert, we have to duplicate the vert and adjust its 
		texture coordinates.
         
		V----S--V
		 \  /\ /
		  v   v

		Since "S" is an on seam vert, we have to split up these two triangles by adding 
		an extra vertex with the same x,y,z coordinates as "S", but with different texture coordinates
		(offset by skinwidth / 2 in the u direction).

		V----V|V--V
		 \  /  \ /
		  v     V
	*/
	void SetupVertexData(IdQuake::aliashdr_t* header, Ogre::Mesh* mesh)
	{	
		Ogre::HardwareVertexBufferSharedPtr vbuffer;
		int sourceIndex = 0;
		size_t offset = 0;
		IdQuake::mdl_t* model = GetModel(header);
		int numVertices = model->numverts;
		int skinWidth = model->skinwidth;
		int skinHeight = model->skinheight;
		const std::vector<int>& onseamVerts = *GetOnseamVerts(header);
		const int FIRST_FRAME_NUMBER = 0;	
		IdQuake::stvert_t* stverts = GetStVerts(header);

		//declare vertex data
		mesh->sharedVertexData->vertexCount = numVertices + onseamVerts.size();
		mesh->sharedVertexData->vertexStart = 0;
		mesh->sharedVertexData->vertexDeclaration->addElement(sourceIndex, offset, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
		offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
		mesh->sharedVertexData->vertexDeclaration->addElement(sourceIndex, offset, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES);
		
		
		//create vertex buffer
		vbuffer = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
				mesh->sharedVertexData->vertexDeclaration->getVertexSize(sourceIndex),
				mesh->sharedVertexData->vertexCount,
				Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY,
				false);
		
		//bind the vertex buffer
		mesh->sharedVertexData->vertexBufferBinding->setBinding(sourceIndex, vbuffer);
		
		//write vertex data to the buffer
		float* pVertex = static_cast<float*>(vbuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD));
		for (int i=0 ; i < (numVertices + onseamVerts.size()) ; i++)
		{
			bool isOnSeam = (i >= numVertices);
			int vindex = isOnSeam ? onseamVerts[i - numVertices] : i;
			Ogre::Vector3 vertex = GetVertex(header, vindex, FIRST_FRAME_NUMBER);
			int offset = isOnSeam ? (model->skinwidth / 2) : 0;

			*pVertex++ = vertex.x;
			*pVertex++ = vertex.y;
			*pVertex++ = vertex.z;
		
			*pVertex++ = ((stverts[vindex].s >> 16) + offset) / static_cast<float>(skinWidth);
			*pVertex++ = (stverts[vindex].t >> 16) / static_cast<float>(skinHeight);
		}

		vbuffer->unlock();
	}

	void SetupBoundingBox(IdQuake::aliashdr_t* header, Ogre::Mesh* mesh)
	{
		IdQuake::mdl_t* model = GetModel(header);
		IdQuake::maliasframedesc_t frame = header->frames[0];

		float scalex = model->scale[0];
		float scaley = model->scale[1];
		float scalez = model->scale[2];

		float offsetx = model->scale_origin[0];
		float offsety = model->scale_origin[1];
		float offsetz = model->scale_origin[2];

		mesh->_setBoundingSphereRadius(model->boundingradius);

		//specify bounding box so that it is rotated 90 degress on Y-Z plane
		mesh->_setBounds(Ogre::AxisAlignedBox((frame.bboxmin.v[0] * scalex) + offsetx,
											  (frame.bboxmin.v[2] * scalez) + offsetz,
											  (frame.bboxmin.v[1] * scaley) + offsety,
											  (frame.bboxmax.v[0] * scalex) + offsetx,										  
											  (frame.bboxmax.v[2] * scalez) + offsetz,
											  (frame.bboxmax.v[1] * scaley) + offsety));
	}

	void CreateTexture(const Ogre::RGBA* rawData, 
					   int width, 
					   int height,
					   const Ogre::String& texName,
					   const Ogre::String& groupName)
	{		
		//return if texture already created
		if (Ogre::TextureManager::getSingleton().resourceExists(texName))
			return;

		Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual(
			texName, //texture name
			groupName, //resource group name
			Ogre::TEX_TYPE_2D,      // type
			width, height,    // width & height
			0,                // number of mipmaps
			Ogre::PF_BYTE_RGBA,     // pixel format
			Ogre::TU_STATIC_WRITE_ONLY);      // usage; 
		
		// Get the pixel buffer
		Ogre::HardwarePixelBufferSharedPtr pixelBuffer = texture->getBuffer();

		// Lock the pixel buffer and get a pixel box
		pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD); // for best performance use HBL_DISCARD!
		const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();

		Ogre::RGBA* pDest = static_cast<Ogre::RGBA*>(pixelBox.data);
		int numPixels = width*height;
		for (int i=0 ; i<numPixels ; i++)	
		{
			pDest[i] = rawData[i];
		}

		// Unlock the pixel buffer
		pixelBuffer->unlock();	
	}

	void CreateMaterial(const Ogre::String& matName, const Ogre::String& texName,
					    const Ogre::String& groupName)
	{	
		//return if material already created
		if (Ogre::MaterialManager::getSingleton().resourceExists(matName))
			return;

		// Create a material using the texture
		Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().create(
			matName,
			groupName);

		material->getTechnique(0)->getPass(0)->createTextureUnitState(texName);
		material->getTechnique(0)->getPass(0)->setSceneBlending(Ogre::SBT_REPLACE);
		material->setTextureFiltering(Ogre::TFO_NONE);	
		material->setTextureAnisotropy(0);
	}

	Ogre::String CreateTexMaterialFromRawData(const Ogre::RGBA* rawData, 
			  								  int width, 
											  int height,
											  const Ogre::String& baseName,
											  const Ogre::String& groupName)
	{	
		const Ogre::String texName = baseName + ".texture";
		const Ogre::String matName = baseName + ".material";

		CreateTexture(rawData, width, height, texName, groupName);
		CreateMaterial(matName, texName, groupName);

		return matName;
	}

	void SetupTexture(IdQuake::aliashdr_t* header, Ogre::Mesh* mesh)
	{
		IdQuake::maliasskindesc_t* skin = GetSkin(header);
		IdQuake::mdl_t* model = GetModel(header);
		byte* skinData = (byte*)header + skin->skin;
		int skinDataSize = model->skinwidth * model->skinheight;

		LoadColorPalette();

		if (skin->type != IdQuake::ALIAS_SKIN_SINGLE)
			OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "Invalid skin type", __FUNCTION__);	

		Ogre::RGBA* textureData = new Ogre::RGBA[skinDataSize];	
		Ogre::RenderSystem* rs = Ogre::Root::getSingleton().getRenderSystem();

		if (!rs->getCapabilities()->hasCapability(Ogre::RSC_NON_POWER_OF_2_TEXTURES))
			OGRE_EXCEPT(Ogre::Exception::ERR_RENDERINGAPI_ERROR, "Hardware does not support non-power-of-two textures", __FUNCTION__);

		for (int i=0 ; i < skinDataSize ; i++)
		{
			rs->convertColourValue(ToColourValue(skinData[i]), textureData + i);
		}
		
		Ogre::String materialName = CreateTexMaterialFromRawData(textureData, 
																model->skinwidth,
																model->skinheight,
																mesh->getName(),
																mesh->getGroup());

		delete[] textureData;

		mesh->getSubMesh(0)->setMaterialName(materialName);
	}

	void ConvertAliasFrameToMesh(IdQuake::aliashdr_t* header, int /*frameNumber*/, Ogre::Mesh* mesh)
	{		
		SetupVertexData(header, mesh);
		SetupTriangles(header, mesh);
		SetupBoundingBox(header, mesh);
		SetupTexture(header, mesh);
	}

	void ConvertAliasToMesh(IdQuake::model_t* mdl, Ogre::Mesh* mesh)
	{
		IdQuake::aliashdr_t* header = static_cast<IdQuake::aliashdr_t*>(mdl->cache.data);
		
		//do some initial Mesh setup
		mesh->createSubMesh()->useSharedVertices = true;
		mesh->sharedVertexData = new Ogre::VertexData();
		
		ConvertAliasFrameToMesh(header, 0, mesh);
		
	}

}

AliasModelLoader::AliasModelLoader(){}
AliasModelLoader::~AliasModelLoader(){}

void AliasModelLoader::loadResource(Ogre::Resource* resource)
{
	Ogre::Mesh* mesh = dynamic_cast<Ogre::Mesh*>(resource);

	if (mesh == NULL)
		return;

	Ogre::DataStreamPtr stream =
            Ogre::ResourceGroupManager::getSingleton().openResource(
				mesh->getName(), mesh->getGroup(), true, resource);
	
	void* buf;
	bool deleteBuf = false;
	
	if (typeid(*stream.get()) == typeid(Ogre::MemoryDataStream))
	{
		buf = static_cast<Ogre::MemoryDataStream*>(stream.get())->getPtr();
	}
	else
	{
		size_t size = stream->size();
		if (size == 0)
			OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "Can't determine size of stream", "");

		buf = new char[size];
		deleteBuf = true;
		if (stream->read(buf, size) != size)
			OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "Error reading from stream", "");
	}

	IdQuake::model_t model;
	model.name = resource->getName();

	IdQuake::Mod_LoadAliasModel(&model, buf);

	ConvertAliasToMesh(&model, mesh);

	IdQuake::Mod_FreeAliasModel(&model);

	if (deleteBuf)
		delete[] buf;
}
